/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
/* smwhere.c - 'where' function for sm_edit */
#define SM_CLASS extern
#include "smlib.h"
#include "smio.h"

CHAR *smwhere_rcsid(void) {
    return("$Id: smwhere.c,v 1.6 2008/02/19 13:15:50 bhepple Exp $");
}

#ifdef __STDC__
extern CHAR *re_comp(CHAR *),
    	    *regex(CHAR *),
	    *bopat[],
    	    *eopat[];
#else
extern CHAR *re_comp(),
    	    *regex(),
	    *bopat[],
    	    *eopat[];
#endif

#define WHERE_VIEW	0
#define WHERE_DELETE 	1
#define WHERE_CHANGE 	2
#define WHERE_OUTPUT 	3

#define WHERE_LESS	0
#define WHERE_LESS_EQ	1
#define WHERE_MORE	2
#define WHERE_MORE_EQ	3
#define WHERE_EQUAL	4

CHAR *where_search_fieldname = NULL;
CHAR *where_relation_fieldname = NULL;
CHAR *where_case_fieldname = NULL;
CHAR *where_match_fieldname = NULL;
CHAR *where_confirm_message = "Press \"Do it\", \"Skip\", \"Do all\" or \"Cancel\"";
CHAR *where_no_records_match_message = "No records found";

static COMMAND delete_trigger ARG3(SM_FIELD, 	field, 
				   COMMAND, 	command, 
				   int, 	window)
{
    SM_SCREEN	scr; /* == where_scr */
    SM_FIELD	fld;

    assert(field);

    scr = field->parent;
    /* If this field goes to 'Y' then set the others to 'N' */
    if (*sm_fld_enq(field) == 'Y') {
	fld = sm_fld_open(scr, "change_it");
	if (fld) {
	    sm_fld_set(fld, "N");
	    sm_paint_fld(window, fld->c_value, fld, 1, 0);
	}
	fld = sm_fld_open(scr, "output_it");
	if (fld) {
	    sm_fld_set(fld, "N");
	    sm_paint_fld(window, fld->c_value, fld, 1, 0);
	}
	/* reset the cursor in the right place etc */
	sm_paint_fld(window, field->c_value, field, 1, 0);
    }
    return(command);
}

static COMMAND change_trigger ARG3(SM_FIELD, 	field, 
				   COMMAND, 	command, 
				   int, 	window)
{
    SM_SCREEN	scr; /* == where_scr */
    SM_FIELD	fld;

    assert(field);

    scr = field->parent;
    /* If this field goes to 'Y' then set the others to 'N' */
    if (*sm_fld_enq(field) == 'Y') {
	fld = sm_fld_open(scr, "output_it");
	if (fld) {
	    sm_fld_set(fld, "N");
	    sm_paint_fld(window, fld->c_value, fld, 1, 0);
	}
	fld = sm_fld_open(scr, "delete_it");
	if (fld) {
	    sm_fld_set(fld, "N");
	    sm_paint_fld(window, fld->c_value, fld, 1, 0);
	}
	/* reset the cursor in the right place etc */
	sm_paint_fld(window, field->c_value, field, 1, 0);
    }
    return(command);
}

static COMMAND output_trigger ARG3(SM_FIELD, 	field, 
				   COMMAND, 	command, 
				   int, 	window)
{
    SM_SCREEN	scr; /* == where_scr */
    SM_FIELD	fld;

    assert(field);

    scr = field->parent;
    /* If this field goes to 'Y' then set the others to 'N' */
    if (*sm_fld_enq(field) == 'Y') {
	fld = sm_fld_open(scr, "change_it");
	if (fld) {
	    sm_fld_set(fld, "N");
	    sm_paint_fld(window, fld->c_value, fld, 1, 0);
	}
	fld = sm_fld_open(scr, "delete_it");
	if (fld) {
	    sm_fld_set(fld, "N");
	    sm_paint_fld(window, fld->c_value, fld, 1, 0);
	}
	/* reset the cursor in the right place etc */
	sm_paint_fld(window, field->c_value, field, 1, 0);
    }
    return(command);
}

/* Return the trigger function associated with a 'WHERE' field */
f_ptr sm_edit_get_field_trigger ARG1(SM_FIELD, field)
{	
    assert(field);

    if (     strcmp(field->trigger_fn, "delete_trigger") == 0)
	return(delete_trigger);
    else if (strcmp(field->trigger_fn, "change_trigger") == 0)
	return(change_trigger);
    else if (strcmp(field->trigger_fn, "output_trigger") == 0)
	return(output_trigger);

    return(NULL);
}

void sm_where_close_file ARG1(struct edit_s *, edit)
{
    assert(edit);

    if (edit->f)
	fclose(edit->f);
    edit->f = NULL;
}

static int where_find_next ARG1(struct edit_s *, edit)
{
    int		where_line, result;
    INDEX	last_found;
    CHAR	*relation, *s;
    int		negate_relation, or_equals;
    CHAR	*value = alloca(SM_MAXREC);
    CHAR	*pattern = alloca(SM_MAXREC);

    assert(edit);

    last_found = smdb_enq_position(edit->base_screen);
    if (smdb_seek(edit->base_screen, edit->start_rec) == NULL)
	return(-1);

    do {
	edit->start_rec = smdb_enq_position(edit->base_screen);
	sm_data_to_screen(edit->base_screen, 
			  smdb_get_current(edit->base_screen), 0);
	for (where_line = 0; where_line < 4; where_line++) {
	    *(SM_LASTCHAR(where_search_fieldname))	= '1' + where_line;
	    *(SM_LASTCHAR(where_relation_fieldname))	= '1' + where_line;
	    *(SM_LASTCHAR(where_case_fieldname)) 	= '1' + where_line;
	    *(SM_LASTCHAR(where_match_fieldname)) 	= '1' + where_line;

	    s = sm_trim(sm_fld_get(sm_where_scr, 
				   where_search_fieldname));
	    if (*s && (*s == '$') && *++s) { /* skip '$' */
		strcpy(value, sm_fld_get(edit->base_screen, s));
		s = sm_fld_get(sm_where_scr, 
			       where_match_fieldname);
		if (*s && (*s == '$') && *++s)
		    /* Then 'pattern' is a field reference */
		    strcpy(pattern, sm_fld_get(edit->base_screen, s));
		else
		    strcpy(pattern, 
			   sm_fld_get(sm_where_scr, where_match_fieldname));
		if (*sm_fld_get(sm_where_scr, where_case_fieldname) == 'Y') {
		    sm_toupper(value);
		    sm_toupper(pattern);
		}
		relation = sm_fld_get(sm_where_scr, where_relation_fieldname);
		if (*relation == '!') {
		    negate_relation = TRUE;
		    relation++;
		} else
		    negate_relation = FALSE;
		if (*relation == '-') { /* regular expression */
		    re_comp(pattern);
		    if (re_exec(value)) {
			if (negate_relation)
			    goto Failed_match;
		    } else
			if (!negate_relation)
			    goto Failed_match;
		} else { /* < or > or = */
		    if ((*relation == '=') || 
			(*(relation + 1) == '='))  /* <= or >= */
			or_equals = TRUE;
		    else
			or_equals = FALSE;
		    result = strcmp(value, pattern);
		    if (result == 0) {
			if (or_equals) {
			    if (negate_relation)
				goto Failed_match;
			} else
			    if (!negate_relation)
				goto Failed_match;
		    } else if (result < 0) {
			if (*relation == '<') {
			    if (negate_relation)
				goto Failed_match;
			} else
			    if (!negate_relation)
				goto Failed_match;
		    } else /* result > 0 */
			if (*relation == '>') {
			    if (negate_relation)
				goto Failed_match;
			} else
			    if (!negate_relation)
				goto Failed_match;
		}
	    }
	}
	/* Line matches all conditions! */
	return(edit->start_rec);

        Failed_match:
	;
    } while (smdb_read_next(edit->base_screen) &&
	     ((edit->end_rec == MAX_INDEX) || (edit->start_rec <= edit->end_rec)));

    smdb_seek(edit->base_screen, last_found);
    return(-1); /* Failure */
}

static int expand_fields ARG3(SM_SCREEN, screen, CHAR *, output, CHAR *, input)
{
    CHAR	*buf = alloca(SM_MAXREC);

    assert(screen && output && input);

    if (sm_expand_fields(screen, buf, input))
	return(SM_ERROR);

    /* Now do any regular expression fritzing about */
    *(SM_LASTCHAR(where_search_fieldname))	= '1';
    *(SM_LASTCHAR(where_relation_fieldname))	= '1';
    *(SM_LASTCHAR(where_case_fieldname)) 	= '1';
    *(SM_LASTCHAR(where_match_fieldname)) 	= '1';
    *output = 0;
    if ((*sm_fld_get(sm_where_scr, where_relation_fieldname) == '-') &&
	*buf) {
	CHAR	*pattern = alloca(200);
	CHAR	*string = alloca(SM_MAXREC);

	strcpy(string, 
	       sm_fld_get(screen, 
			  sm_trim(sm_fld_get(sm_where_scr, 
					     where_search_fieldname)) + 1));
	strcpy(pattern, sm_fld_get(sm_where_scr, where_match_fieldname));
	if (*sm_fld_get(sm_where_scr, where_case_fieldname) == 'Y') {
	    sm_toupper(string);
	    sm_toupper(pattern);
	}
	re_comp(pattern);
	re_exec(string);
	/* restore string in case it was upper cased - bopat & eopat 
	   point into it ready for re_subs */
	strcpy(string, 
	       sm_fld_get(screen, 
			  sm_trim(sm_fld_get(sm_where_scr, 
					     where_search_fieldname)) + 1));
	re_subs(buf, output);
    } else
	strcpy(output, buf);
    return(0);
}

static int where_action_record ARG1(struct edit_s *, edit)
{
    assert(edit);

    if        (edit->where_action == WHERE_DELETE) {
	if (smdb_delete_rec(edit->base_screen))
	    return(SM_ERROR);

    } else if (edit->where_action == WHERE_CHANGE) {
	CHAR	*buf = alloca(SM_MAXREC);
	
	sm_data_to_screen(edit->base_screen, 
			  smdb_get_current(edit->base_screen), 0);
	if (expand_fields(edit->base_screen,
			  buf, 
			  sm_fld_get(sm_where_scr, "change_to")))
	    return(SM_ERROR); /* was checked already, but why not? */
	sm_fld_set(sm_fld_open(edit->base_screen, 
			       sm_fld_get(sm_where_scr, "change_fld") + 1),
		   buf);
	sm_update_record_timestamp(edit->base_screen);
	sm_screen_to_data(buf, edit->base_screen);
	smdb_set_current(edit->base_screen, buf);
    } else if (edit->where_action == WHERE_OUTPUT) {
	
	if ((fputs(smdb_get_current(edit->base_screen), edit->f) == EOF) ||
	    (fputc('\n', edit->f) == EOF)) {
	    sm_file_error("Record NOT written", "", errno);
	    return(SM_ERROR);
	}
    } /* else do nothing ... WHERE_VIEW */
    return(0);
}

static void where_display_it ARG1(struct edit_s *, edit)
{
    assert(edit);

    sm_data_to_screen(edit->base_screen, 
		      smdb_get_current(edit->base_screen), 0);
    sm_paint_scr(SM_BASE_WINDOW, edit->base_screen, /* enhance = */ 1);
}

static CHAR *relations[] = {
    "-", "!-", "=", ">=", "<=", "<", ">", "!=", "!<", "!>", NULL
    };
int check_relation ARG1(CHAR *, s)
{
    CHAR	**relation;

    assert(relation);

    for (relation = relations; *relation; relation++)
	if (strcmp(*relation, s) == 0) 
	    return(0);
    return(1);
}

CALLBACK(where_exec)
{
    struct edit_s 	*edit = (struct edit_s *) callback_data;

    assert(edit);

    switch (command) {
    case SM_WHERE_CONFIRM_C:
	if (where_action_record(edit))
	    return(SM_POPCALLBACK);
    case SM_WHERE_SKIP_C:
	if ((command == SM_WHERE_SKIP_C) ||
	    (edit->where_action != WHERE_DELETE))
	    edit->start_rec++;
	if (where_find_next(edit) >= 0) {
	    sm_message(where_confirm_message);
	    where_display_it(edit);
	} else
	    sm_message(where_no_records_match_message);
	return(0);
    case SM_WHERE_DO_ALL_C:
	do {
	    if (where_action_record(edit))
		return(SM_POPCALLBACK);
	    if (edit->where_action != WHERE_DELETE)
		edit->start_rec++;
	} while (where_find_next(edit) >= 0);
    case SM_QUIT_C:
	return(SM_POPCALLBACK);
    default:
	return(command);
    }
}

CALLBACK(do_where)
{
    struct edit_s 	*edit = (struct edit_s *) callback_data;
    CHAR		*s;
    int			i;
    INDEX		store_pos;

    assert(edit);

    if ((command == SM_QUIT_C) || 
	(command == SM_EXIT_C) ||
	(command == SM_NO_SAVE_EXIT_C) ||
	(command == SM_STORE_FILE_C) ||
	(command == SM_STORE_FILE_AS_C) ||
	(command == SM_LOAD_FILE_C) ||
	(command == SM_CLEAR_FILE_C))
	goto Finished_where;

    /* Check form ... */
    /* Check record #'s */
    edit->start_rec = AsciiToIndex(sm_fld_get(sm_where_scr, "start_rec")) - 1;
    if (edit->start_rec < (INDEX) 0) {
	sm_message("STARTING RECORD #: Too low (use '1' for start-of-file)");
	sm_set_field(sm_fld_open(sm_where_scr, "start_rec"));
	goto Redo_form;
    }
    if (edit->start_rec >= smdb_num_records(edit->base_screen)) {
	sm_message("STARTING RECORD #: Too high (use \'$\' for end-of-file)");
	sm_set_field(sm_fld_open(sm_where_scr, "start_rec"));
	goto Redo_form;
    }

    s = sm_fld_get(sm_where_scr, "end_rec");
    if (strcmp(sm_trim(s), "$") == 0)
	edit->end_rec = MAX_INDEX;
    else {
	edit->end_rec = AsciiToIndex(s) - 1;
	if ((edit->end_rec < (INDEX) 0) || (edit->end_rec < edit->start_rec)) {
	    sm_message("ENDING RECORD #: Bad value");
	    sm_set_field(sm_fld_open(sm_where_scr, "end_rec"));
	    goto Redo_form;
	}
    }	

    /* Check that fields exist, relation & regular expression */
    /* WHERE lines */
    for (i = 0; i < 4; i++) {
	*(SM_LASTCHAR(where_search_fieldname))	= '1' + i;
	*(SM_LASTCHAR(where_relation_fieldname))= '1' + i;
	*(SM_LASTCHAR(where_case_fieldname)) 	= '1' + i;
	*(SM_LASTCHAR(where_match_fieldname)) 	= '1' + i;
	
	s = sm_trim(sm_fld_get(sm_where_scr, where_search_fieldname));
	if (*s) {
	    if ((*s != '$') || 
		(*(s + 1) == 0) ||
		(sm_fld_open(edit->base_screen, s + 1) == NULL)) {
		CHAR	*buf = alloca(100);

		sprintf(buf, "No such field: %s", s);
		sm_message(buf);
		sm_set_field(sm_fld_open(sm_where_scr, where_search_fieldname));
		goto Redo_form;
	    }
	    s = sm_trim(sm_fld_get(sm_where_scr, where_relation_fieldname));
	    if (check_relation(s)) {
		CHAR	*buf = alloca(100);

		sprintf(buf, "Bad relation: %s", s);
		sm_message(buf);
		sm_set_field(sm_fld_open(sm_where_scr, where_relation_fieldname));
		goto Redo_form;
	    }	
	    if ((strcmp(s, "-") == 0) ||
		(strcmp(s, "!-")== 0)) {
		/* Then we are to do a regular expression match */
		s = sm_fld_get(sm_where_scr, where_match_fieldname);
		if (re_comp(s)) {
		    CHAR	buf[120];
		    
		    sprintf(buf, "Bad regular expression: %s", re_comp(s));
		    sm_message(buf);
		    sm_set_field(sm_fld_open(sm_where_scr, 
					     where_match_fieldname));
		    goto Redo_form;
		}
	    }
	}
    }

    edit->where_action = WHERE_VIEW;

    s = sm_trim(sm_fld_get(sm_where_scr, "delete_it"));
    if (*s == 'Y')
	edit->where_action = WHERE_DELETE;

    /* If change_it, check that field to be changed exists */
    s = sm_trim(sm_fld_get(sm_where_scr, "change_it"));
    if (*s == 'Y') {
	CHAR	*buf = alloca(SM_MAXREC);

	edit->where_action = WHERE_CHANGE;
	s = sm_trim(sm_fld_get(sm_where_scr, "change_fld"));
	if ((*s != '$') || 
	    (*(s + 1) == 0) ||
	    (sm_fld_open(edit->base_screen, s + 1) == NULL)) {
	    CHAR	*buf = alloca(100);
	    
	    sprintf(buf, "CHANGE: No such field: %s", s);
	    sm_message(buf);
	    sm_set_field(sm_fld_open(sm_where_scr, "change_fld"));
	    goto Redo_form;
	}
	s = sm_fld_get(sm_where_scr, "change_to");
	if (expand_fields(edit->base_screen, buf, s)) {
	    CHAR	*buf = alloca(100);
	    sm_set_field(sm_fld_open(sm_where_scr, "change_to"));
	    
	    sprintf(buf, "CHANGE TO: No such field: %s", s);
	    sm_message(buf);
	    goto Redo_form;
	}
    }

    /* If output, check that file does not exist, else overwrite? */
    sm_where_close_file(edit);
    s = sm_trim(sm_fld_get(sm_where_scr, "output_it"));
    if (*s == 'Y') {
	edit->where_action = WHERE_OUTPUT;
	s = sm_trim(sm_fld_get(sm_where_scr, "filename"));
	if (!*s) {
	    sm_message("No filename specified.");
	    sm_set_field(sm_fld_open(sm_where_scr, "filename"));
	    goto Redo_form;
	}
	edit->f = fopen(s, "r");
	if (edit->f) {
	    fclose(edit->f);
	    if (sm_confirm("File exists - overwrite it?") != SM_YES_C) {
		sm_message("File NOT overwritten!");
		sm_set_field(sm_fld_open(sm_where_scr, "filename"));
		goto Redo_form;
	    }
	}
	edit->f = fopen(s, "w");
	if (!(edit->f)) {
	    CHAR	*buf = alloca(100);
	    
	    sprintf(buf, "Can't open\"%s\" for writing!", s);
	    sm_message(buf);
	    sm_set_field(sm_fld_open(sm_where_scr, "filename"));
	    goto Redo_form;
	}
    }

    /* At this point the form is OK - let's boogie ... */
    /* Find the first record that conforms */
    store_pos = smdb_enq_position(edit->base_screen);
    if (where_find_next(edit) < 0) {
	smdb_seek(edit->base_screen, store_pos);
	sm_message(where_no_records_match_message);
	goto Redo_form;
    }

    switch (command) {
    case SM_WHERE_CONFIRM_C:
	where_display_it(edit);
	sm_message(where_confirm_message);
	sm_paint_skey(sm_where_exec_skey);
	sm_add_callback(where_exec, NULL);
	return(0);

    case SM_WHERE_DO_ALL_C:
	where_display_it(edit);
	do {
	    if (where_action_record(edit)) {
		command = SM_POPCALLBACK;
		goto Finished_where;
	    }
	    if (edit->where_action != WHERE_DELETE)
		edit->start_rec++;
	} while (where_find_next(edit) >= 0);

    default:
	goto Redo_form;
    }

  Finished_where:
    sm_where_close_file(edit);
    sm_paint_skey(edit->set_in_use);
    return(command);

  Redo_form:
    sm_setup_scr(SM_BASE_WINDOW, 
		 sm_where_scr, 
		 NULL, 
		 edit->current_screen, 
		 0, 0, 0);
    return(0);
}

void sm_setup_where ARG1(struct edit_s *, edit)
{
    assert(edit);

    SM_ASSIGN(where_search_fieldname, "searchN");
    SM_ASSIGN(where_relation_fieldname, "relationN");
    SM_ASSIGN(where_case_fieldname, "caseN");
    SM_ASSIGN(where_match_fieldname, "matchN");

    sm_add_callback(do_where, NULL); 
    sm_setup_scr(SM_BASE_WINDOW, 
		 sm_where_scr, 
		 NULL, 
		 edit->current_screen, 
		 0, 0, 0);
}

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
